module Kit.Compiler.Passes.CompileCode (compileCode) where

import Control.Concurrent.Async
import Control.Monad
import Data.Either
import Data.List
import Data.Mutable
import System.Directory
import System.FilePath
import System.Process
import Kit.Ast
import Kit.Compiler.Context
import Kit.Compiler.Utils
import Kit.Error
import Kit.Log
import Kit.Str
import Kit.Toolchain

{-
  Compile the generated C code.
-}
compileCode :: CompileContext -> Toolchain -> [TypePath] -> IO (Maybe FilePath)
compileCode ctx cc names = do
  createDirectoryIfMissing True (buildDir ctx)
  if ctxSinglePass ctx
    then do
      -- compile and link in one pass
      if ctxNoLink ctx
        then return Nothing
        else do
          binPath <- linkFiles ctx cc (map (libPath ctx) names) True
          return $ Just binPath
    else do
      -- compile individually in parallel, then link
      printLogIf ctx "compiling"
      results <- forConcurrently names (compileBundle ctx cc)
      errs    <- foldM
        (\acc out -> case out of
          Left  err -> return $ (BasicError err Nothing) : acc
          Right s   -> do
            putStr s
            return acc
        )
        []
        results
      when (not $ null errs) $ throwk $ KitErrors $ reverse $ map KitError errs
      if ctxNoLink ctx
        then return Nothing
        else do
          binPath <- linkBundles ctx cc names
          return $ Just binPath

compileBundle
  :: CompileContext -> Toolchain -> TypePath -> IO (Either String String)
compileBundle ctx cc name = do
  let objFilePath = objPath ctx name
  createDirectoryIfMissing True (takeDirectory objFilePath)
  debugLog ctx $ "compiling " ++ s_unpack (showTypePath name)
  invokeCompiler
    (cc { ccIncludePaths = (includeDir ctx) : (ccIncludePaths cc) })
    (debugLog ctx)
    (not $ ctxNoCcache ctx)
    (libPath ctx name)
    (objPath ctx name)

linkBundles :: CompileContext -> Toolchain -> [TypePath] -> IO FilePath
linkBundles ctx cc names =
  linkFiles ctx cc [ objPath ctx name | name <- names ] False

linkFiles :: CompileContext -> Toolchain -> [FilePath] -> Bool -> IO FilePath
linkFiles ctx cc files source = do
  let outName = ctxOutputPath ctx
  when (ctxVerbose ctx >= 0) $ printLog $ "linking"
  linkedLibs <- readRef $ ctxLinkedLibs ctx
  invokeLinker
    (cc
      { ccIncludePaths = (includeDir ctx) : (ccIncludePaths cc)
      , ldFlags        = (ldFlags cc)
        ++ [ "-l" ++ s_unpack lib | lib <- nub linkedLibs ]
      }
    )
    (debugLog ctx)
    (ctxIsLibrary ctx)
    files
    source
    outName
